/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.filesys.server.auth.passthru;

import java.io.IOException;

import org.filesys.netbios.NetBIOSSession;
import org.filesys.netbios.NetworkSession;
import org.filesys.netbios.RFCNetBIOSProtocol;
import org.filesys.smb.PacketTypeV1;
import org.filesys.smb.SMBException;
import org.filesys.smb.SMBStatus;
import org.filesys.util.DataPacker;

/**
 * SMB packet type class
 *
 * @author gkspencer
 */
public class SMBPacket {

    //	SMB packet offsets, assuming an RFC NetBIOS transport
    public static final int SIGNATURE       = RFCNetBIOSProtocol.HEADER_LEN;
    public static final int COMMAND         = 4 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERRORCODE       = 5 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERRORCLASS      = 5 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERROR           = 7 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int FLAGS           = 9 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int FLAGS2          = 10 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PIDHIGH         = 12 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int SID             = 18 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int SEQNO           = 20 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int TID             = 24 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PID             = 26 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int UID             = 28 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int MID             = 30 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int WORDCNT         = 32 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ANDXCOMMAND     = 33 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ANDXRESERVED    = 34 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PARAMWORDS      = 33 + RFCNetBIOSProtocol.HEADER_LEN;

    //	SMB packet header length for a transaction type request
    public static final int TRANS_HEADERLEN = 66 + RFCNetBIOSProtocol.HEADER_LEN;

    //	Minimum receive length for a valid SMB packet
    public static final int MIN_RXLEN = 32;

    //	Default buffer size to allocate for SMB packets
    public static final int DEFAULT_BUFSIZE = 4096;

    //	Flag bits
    public static final int FLG_SUBDIALECT  = 0x01;
    public static final int FLG_CASELESS    = 0x08;
    public static final int FLG_CANONICAL   = 0x10;
    public static final int FLG_OPLOCK      = 0x20;
    public static final int FLG_NOTIFY      = 0x40;
    public static final int FLG_RESPONSE    = 0x80;

    //	Flag2 bits
    public static final int FLG2_LONGFILENAMES      = 0x0001;
    public static final int FLG2_EXTENDEDATTRIB     = 0x0002;
    public static final int FLG2_SECURITYSIG        = 0x0004;
    public static final int FLG2_EXTENDEDSECURITY   = 0x0800;
    public static final int FLG2_READIFEXE          = 0x2000;
    public static final int FLG2_LONGERRORCODE      = 0x4000;
    public static final int FLG2_UNICODE            = 0x8000;

    //	Security mode bits
    public static final int SEC_USER    = 0x0001;
    public static final int SEC_ENCRYPT = 0x0002;

    //	Raw mode bits
    public static final int RAW_READ    = 0x0001;
    public static final int RAW_WRITE   = 0x0002;

    //	SMB packet buffer
    private byte[] m_smbbuf;

    //	Packet type
    private int m_pkttype;

    //	Current byte area pack/unpack position
    protected int m_pos;
    protected int m_endpos;

    //	Time of last packet send
    protected long m_lastTxTime;

    /**
     * Default constructor
     */
    public SMBPacket() {
        m_smbbuf = new byte[DEFAULT_BUFSIZE];
        InitializeBuffer();
    }

    /**
     * Construct an SMB packet using the specified packet buffer.
     *
     * @param buf SMB packet buffer.
     */
    public SMBPacket(byte[] buf) {
        m_smbbuf = buf;
    }

    /**
     * Construct an SMB packet of the specified size.
     *
     * @param siz Size of SMB packet buffer to allocate.
     */
    public SMBPacket(int siz) {
        m_smbbuf = new byte[siz];
        InitializeBuffer();
    }

    /**
     * Check if a received SMB is valid, if not then throw an exception
     *
     * @exception SMBException SMB error
     */
    public final void checkForError()
            throws SMBException {

        //  Check if a valid SMB response has been received
        if (isValidResponse() == false) {

            //	Check for NT error codes
            if (isLongErrorCode())
                throw new SMBException(SMBStatus.NTErr, getLongErrorCode());
            else
                throw new SMBException(getErrorClass(), getErrorCode());
        }
    }

    /**
     * Clear the data byte count
     */
    public final void clearBytes() {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort(0, m_smbbuf, offset);
    }

    /**
     * Check if the error class/code match the specified error/class
     *
     * @param errClass int
     * @param errCode  int
     * @return boolean
     */
    public final boolean equalsError(int errClass, int errCode) {
        if (getErrorClass() == errClass && getErrorCode() == errCode)
            return true;
        return false;
    }

    /**
     * Send the SMB packet and receive the response packet
     *
     * @param sess     Network session to send/receive the packet over.
     * @param rxPkt    SMB packet to receive the response into.
     * @param throwerr If true then throw an I/O error if an invalid response is
     *                 received.
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    protected final synchronized void ExchangeLowLevelSMB(NetworkSession sess, SMBPacket rxPkt, boolean throwerr)
            throws java.io.IOException, SMBException {

        //	Set multiplex id
        if (getMultiplexId() == 0)
            setMultiplexId(1);

        //	Send the SMB request
        sess.Send(m_smbbuf, getLength());

        //	Receive a response
        if (sess.Receive(rxPkt.getBuffer()) >= MIN_RXLEN) {

            //	Check if the response is for the current request
            if (rxPkt.getCommand() == m_pkttype) {

                //  Check if a valid SMB response has been received
                if (throwerr == true)
                    checkForError();

                //	Valid packet received, return to caller
                return;
            }
        }

        //	Invalid receive packet
        throw new java.io.IOException("Invalid SMB Receive Packet");
    }

    /**
     * Send/receive an SMB protocol packet to the remote server.
     *
     * @param sess  SMB session to send/receive data on.
     * @param rxPkt SMB packet to receive the response into.
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    public synchronized final void ExchangeSMB(AuthenticateSession sess, SMBPacket rxPkt)
            throws SMBException, IOException {

        //  Call the main SMB exhchange method
        ExchangeSMB(sess, rxPkt, false);
    }

    /**
     * Send the SMB packet and receive the response packet
     *
     * @param sess     SMB session to send/receive the packet over.
     * @param rxPkt    SMB packet to receive the response into.
     * @param throwerr If true then throw an I/O error if an invalid response is
     *                 received.
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    public synchronized final void ExchangeSMB(AuthenticateSession sess, SMBPacket rxPkt, boolean throwerr)
            throws SMBException, IOException {

        //	Set the process id, user id and multiplex id
        setProcessId(sess.getProcessId());
        setUserId(sess.getUserId());

        if (getMultiplexId() == 0)
            setMultiplexId(1);

        //	Get the network session
        NetworkSession netSess = sess.getSession();

        //	Send the SMB request
        netSess.Send(m_smbbuf, getLength());

        //	Receive the response, other asynchronous responses may be received before the response for this request
        boolean rxValid = false;

        while (rxValid == false) {

            //	Receive a response
            if (netSess.Receive(rxPkt.getBuffer()) >= MIN_RXLEN) {

                //	Check if the response is for the current request
                if (rxPkt.getCommand() == m_pkttype) {

                    //  Check if a valid SMB response has been received
                    if (throwerr == true)
                        checkForError();

                    //	Valid packet received, return to caller
                    return;
                }
            }
        }

        //	Invalid receive packet
        throw new java.io.IOException("Invalid SMB Receive Packet");
    }

    /**
     * Get the secondary command code
     *
     * @return Secondary command code
     */
    public final int getAndXCommand() {
        return m_smbbuf[ANDXCOMMAND] & 0xFF;
    }

    /**
     * Return the byte array used for the SMB packet
     *
     * @return Byte array used for the SMB packet.
     */
    public final byte[] getBuffer() {
        return m_smbbuf;
    }

    /**
     * Return the total buffer size available to the SMB request
     *
     * @return Total SMB buffer length available.
     */
    public final int getBufferLength() {
        return m_smbbuf.length - RFCNetBIOSProtocol.HEADER_LEN;
    }

    /**
     * Return the available buffer space for data bytes
     *
     * @return int
     */
    public final int getAvailableLength() {
        return m_smbbuf.length - DataPacker.longwordAlign(getByteOffset());
    }

    /**
     * Get the data byte count for the SMB packet
     *
     * @return Data byte count
     */
    public final int getByteCount() {

        //	Calculate the offset of the byte count
        int pos = PARAMWORDS + (2 * getParameterCount());
        return DataPacker.getIntelShort(m_smbbuf, pos);
    }

    /**
     * Get the data byte area offset within the SMB packet
     *
     * @return Data byte offset within the SMB packet.
     */
    public final int getByteOffset() {

        //	Calculate the offset of the byte buffer
        int pCnt = getParameterCount();
        int pos = WORDCNT + (2 * pCnt) + 3;
        return pos;
    }

    /**
     * Get the SMB command
     *
     * @return SMB command code.
     */
    public final int getCommand() {
        return m_smbbuf[COMMAND] & 0xFF;
    }

    /**
     * Determine if normal or long error codes have been returned
     *
     * @return boolean
     */
    public final boolean hasLongErrorCode() {
        if ((getFlags2() & FLG2_LONGERRORCODE) == 0)
            return false;
        return true;
    }

    /**
     * Return the saved packet type
     *
     * @return int
     */
    public final int isType() {
        return m_pkttype;
    }

    /**
     * Check if the packet contains ASCII or Unicode strings
     *
     * @return boolean
     */
    public final boolean isUnicode() {
        return (getFlags2() & FLG2_UNICODE) != 0 ? true : false;
    }

    /**
     * Check if the packet is using caseless filenames
     *
     * @return boolean
     */
    public final boolean isCaseless() {
        return (getFlags() & FLG_CASELESS) != 0 ? true : false;
    }

    /**
     * Check if long file names are being used
     *
     * @return boolean
     */
    public final boolean isLongFileNames() {
        return (getFlags2() & FLG2_LONGFILENAMES) != 0 ? true : false;
    }

    /**
     * Check if long error codes are being used
     *
     * @return boolean
     */
    public final boolean isLongErrorCode() {
        return (getFlags2() & FLG2_LONGERRORCODE) != 0 ? true : false;
    }

    /**
     * Get the SMB error class
     *
     * @return SMB error class.
     */
    public final int getErrorClass() {
        return m_smbbuf[ERRORCLASS] & 0xFF;
    }

    /**
     * Get the SMB error code
     *
     * @return SMB error code.
     */
    public final int getErrorCode() {
        return m_smbbuf[ERROR] & 0xFF;
    }

    /**
     * Get the SMB flags value.
     *
     * @return SMB flags value.
     */
    public final int getFlags() {
        return m_smbbuf[FLAGS] & 0xFF;
    }

    /**
     * Get the SMB flags2 value.
     *
     * @return SMB flags2 value.
     */
    public final int getFlags2() {
        return DataPacker.getIntelShort(m_smbbuf, FLAGS2);
    }

    /**
     * Calculate the total used packet length.
     *
     * @return Total used packet length.
     */
    public final int getLength() {
        return (getByteOffset() + getByteCount()) - SIGNATURE;
    }

    /**
     * Get the long SMB error code
     *
     * @return Long SMB error code.
     */
    public final int getLongErrorCode() {
        return DataPacker.getIntelInt(m_smbbuf, ERRORCODE);
    }

    /**
     * Get the multiplex identifier.
     *
     * @return Multiplex identifier.
     */
    public final int getMultiplexId() {
        return DataPacker.getIntelShort(m_smbbuf, MID);
    }

    /**
     * Get a parameter word from the SMB packet.
     *
     * @param idx Parameter index (zero based).
     * @return Parameter word value.
     * @exception IndexOutOfBoundsException If the parameter index is out of range.
     */
    public final int getParameter(int idx) throws java.lang.IndexOutOfBoundsException {

        //	Range check the parameter index
        if (idx > getParameterCount())
            throw new java.lang.IndexOutOfBoundsException();

        //	Calculate the parameter word offset
        int pos = WORDCNT + (2 * idx) + 1;
        return DataPacker.getIntelShort(m_smbbuf, pos) & 0xFFFF;
    }

    /**
     * Get the specified parameter words, as an int value.
     *
     * @param idx Parameter index (zero based).
     * @return int
     */
    public final int getParameterLong(int idx) {
        int pos = WORDCNT + (2 * idx) + 1;
        return DataPacker.getIntelInt(m_smbbuf, pos);
    }

    /**
     * Get the parameter count
     *
     * @return Parameter word count.
     */
    public final int getParameterCount() {
        return m_smbbuf[WORDCNT];
    }

    /**
     * Get the process indentifier (PID)
     *
     * @return Process identifier value.
     */
    public final int getProcessId() {
        return DataPacker.getIntelShort(m_smbbuf, PID);
    }

    /**
     * Get the tree identifier (TID)
     *
     * @return Tree identifier (TID)
     */
    public final int getTreeId() {
        return DataPacker.getIntelShort(m_smbbuf, TID);
    }

    /**
     * Get the user identifier (UID)
     *
     * @return User identifier (UID)
     */
    public final int getUserId() {
        return DataPacker.getIntelShort(m_smbbuf, UID);
    }

    /**
     * Return the last sent packet time
     *
     * @return long
     */
    public final long getLastPacketSendTime() {
        return m_lastTxTime;
    }

    /**
     * Initialize the SMB packet buffer.
     */
    private final void InitializeBuffer() {

        //	Set the packet signature
        m_smbbuf[SIGNATURE] = (byte) 0xFF;
        m_smbbuf[SIGNATURE + 1] = (byte) 'S';
        m_smbbuf[SIGNATURE + 2] = (byte) 'M';
        m_smbbuf[SIGNATURE + 3] = (byte) 'B';
    }

    /**
     * Determine if this packet is an SMB response, or command packet
     *
     * @return true if this SMB packet is a response, else false
     */
    public final boolean isResponse() {
        int resp = getFlags();
        if ((resp & FLG_RESPONSE) != 0)
            return true;
        return false;
    }

    /**
     * Check if the response packet is valid, ie. type and flags
     *
     * @return true if the SMB packet is a response packet and the response is
     * valid, else false.
     */
    public final boolean isValidResponse() {

        //	Check if this is a response packet, and the correct type of packet
        if (isResponse() && getCommand() == m_pkttype) {

            //	Check if standard error codes or NT 32-bit error codes are being used
            if ((getFlags2() & FLG2_LONGERRORCODE) == 0) {
                if (getErrorCode() == SMBStatus.Success)
                    return true;
            } else if (getLongErrorCode() == SMBStatus.NTSuccess)
                return true;
        }
        return false;
    }

    /**
     * Pack a byte (8 bit) value into the byte area
     *
     * @param val byte
     */
    public final void packByte(byte val) {
        m_smbbuf[m_pos++] = val;
    }

    /**
     * Pack a byte (8 bit) value into the byte area
     *
     * @param val int
     */
    public final void packByte(int val) {
        m_smbbuf[m_pos++] = (byte) val;
    }

    /**
     * Pack the specified bytes into the byte area
     *
     * @param byts byte[]
     * @param len  int
     */
    public final void packBytes(byte[] byts, int len) {
        System.arraycopy(byts, 0, m_smbbuf, m_pos, len);
        m_pos += len;
    }

    /**
     * Pack a string using either ASCII or Unicode into the byte area
     *
     * @param str String
     * @param uni boolean
     */
    public final void packString(String str, boolean uni) {

        //	Check for Unicode or ASCII
        if (uni) {

            //	Word align the buffer position, pack the Unicode string
            m_pos = DataPacker.wordAlign(m_pos);
            DataPacker.putUnicodeString(str, m_smbbuf, m_pos, true);
            m_pos += (str.length() * 2) + 2;
        } else {

            //	Pack the ASCII string
            DataPacker.putString(str, m_smbbuf, m_pos, true);
            m_pos += str.length() + 1;
        }
    }

    /**
     * Pack a word (16 bit) value into the byte area
     *
     * @param val int
     */
    public final void packWord(int val) {
        DataPacker.putIntelShort(val, m_smbbuf, m_pos);
        m_pos += 2;
    }

    /**
     * Pack a 32 bit integer value into the byte area
     *
     * @param val int
     */
    public final void packInt(int val) {
        DataPacker.putIntelInt(val, m_smbbuf, m_pos);
        m_pos += 4;
    }

    /**
     * Pack a long integer (64 bit) value into the byte area
     *
     * @param val long
     */
    public final void packLong(long val) {
        DataPacker.putIntelLong(val, m_smbbuf, m_pos);
        m_pos += 8;
    }

    /**
     * Return the current byte area buffer position
     *
     * @return int
     */
    public final int getPosition() {
        return m_pos;
    }

    /**
     * Set the byte area buffer position
     *
     * @param pos int
     */
    public final void setPosition(int pos) {
        m_pos = pos;
    }

    /**
     * Unpack a byte value from the byte area
     *
     * @return int
     */
    public final int unpackByte() {
        return (int) m_smbbuf[m_pos++];
    }

    /**
     * Unpack a block of bytes from the byte area
     *
     * @param len int
     * @return byte[]
     */
    public final byte[] unpackBytes(int len) {
        if (len <= 0)
            return null;

        byte[] buf = new byte[len];
        System.arraycopy(m_smbbuf, m_pos, buf, 0, len);
        m_pos += len;
        return buf;
    }

    /**
     * Unpack a word (16 bit) value from the byte area
     *
     * @return int
     */
    public final int unpackWord() {
        int val = DataPacker.getIntelShort(m_smbbuf, m_pos);
        m_pos += 2;
        return val;
    }

    /**
     * Unpack an integer (32 bit) value from the byte/parameter area
     *
     * @return int
     */
    public final int unpackInt() {
        int val = DataPacker.getIntelInt(m_smbbuf, m_pos);
        m_pos += 4;
        return val;
    }

    /**
     * Unpack a long integer (64 bit) value from the byte area
     *
     * @return long
     */
    public final long unpackLong() {
        long val = DataPacker.getIntelLong(m_smbbuf, m_pos);
        m_pos += 8;
        return val;
    }

    /**
     * Unpack a string from the byte area
     *
     * @param uni boolean
     * @return String
     */
    public final String unpackString(boolean uni) {

        //	Check for Unicode or ASCII
        String ret = null;

        if (uni) {

            //	Word align the current buffer position
            m_pos = DataPacker.wordAlign(m_pos);
            ret = DataPacker.getUnicodeString(m_smbbuf, m_pos, 255);
            if (ret != null)
                m_pos += (ret.length() * 2) + 2;
        } else {

            //	Unpack the ASCII string
            ret = DataPacker.getString(m_smbbuf, m_pos, 255);
            if (ret != null)
                m_pos += ret.length() + 1;
        }

        //	Return the string
        return ret;
    }

    /**
     * Unpack a string from the byte area
     *
     * @param len int
     * @param uni boolean
     * @return String
     */
    public final String unpackString(int len, boolean uni) {

        //	Check for Unicode or ASCII
        String ret = null;

        if (uni) {

            //	Word align the current buffer position
            m_pos = DataPacker.wordAlign(m_pos);
            ret = DataPacker.getUnicodeString(m_smbbuf, m_pos, len);
            if (ret != null)
                m_pos += (ret.length() * 2);
        } else {

            //	Unpack the ASCII string
            ret = DataPacker.getString(m_smbbuf, m_pos, len);
            if (ret != null)
                m_pos += ret.length();
        }

        //	Return the string
        return ret;
    }

    /**
     * Check if there is more data in the byte area
     *
     * @return boolean
     */
    public final boolean hasMoreData() {
        if (m_pos < m_endpos)
            return true;
        return false;
    }

    /**
     * Receive an SMB response packet.
     *
     * @param sess NetBIOS session to receive the SMB packet on.
     * @exception IOException If an I/O error occurs.
     */
    private final void ReceiveSMB(NetBIOSSession sess)
            throws java.io.IOException {

        if (sess.Receive(m_smbbuf) >= MIN_RXLEN)
            return;

        //	Not enough data received for an SMB header
        throw new java.io.IOException("Short NetBIOS receive");
    }

    /**
     * Receive an SMB packet on the spceified SMB session.
     *
     * @param sess SMB session to receive the packet on.
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    protected final void ReceiveSMB(AuthenticateSession sess)
            throws java.io.IOException, SMBException {

        //	Call the main receive method
        ReceiveSMB(sess, true);
    }

    /**
     * Receive an SMB packet on the spceified SMB session.
     *
     * @param sess     SMB session to receive the packet on.
     * @param throwErr Flag to indicate if an error is thrown if an error response is received
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    protected final void ReceiveSMB(AuthenticateSession sess, boolean throwErr)
            throws java.io.IOException, SMBException {

        //	Get the network session
        NetworkSession netSess = sess.getSession();

        //	Receive the response, other asynchronous responses may be received before the response for this request
        boolean rxValid = false;

        while (rxValid == false) {

            //	Receive a response
            if (netSess.Receive(getBuffer()) >= MIN_RXLEN) {

                //	Check if the response is for the current request
                if (getCommand() == m_pkttype) {

                    //  Check if a valid SMB response has been received
                    if (throwErr == true)
                        checkForError();

                    //	Valid packet received, return to caller
                    return;
                }

                //	Asynchronous response received, pass the packet to the session for processing
                sess.processAsynchResponse(this);
            } else {

                //	Not enough data received for an SMB header
                throw new java.io.IOException("Short NetBIOS receive");
            }
        }
    }

    /**
     * Receive an asynchronous SMB response from the server
     *
     * @param sess     Session
     * @param waitTime Receive timeout in milliseconds, or zero for no timeout
     * @exception IOException   If a network error occurs.
     * @exception SMBException  If an SMB level error occurs
     */
    protected final void ReceiveAsynchSMB(AuthenticateSession sess, int waitTime)
            throws java.io.IOException, SMBException {

        //	Get the network session
        NetworkSession netSess = sess.getSession();
        netSess.setTimeout(waitTime);

        //	Receive, or wait for, a response
        if (netSess.Receive(getBuffer()) >= MIN_RXLEN) {

            //	Asynchronous response received, pass the packet to the session for processing
            sess.processAsynchResponse(this);
        } else {

            //	Not enough data received for an SMB header
            throw new java.io.IOException("Short NetBIOS receive");
        }
    }

    /**
     * Send the SMB packet on the specified SMB session.
     *
     * @param sess SMB session to send this packet over.
     * @exception IOException   If a network error occurs.
     */
    protected final void SendSMB(AuthenticateSession sess) throws java.io.IOException {

        //	Update the last send time
        m_lastTxTime = System.currentTimeMillis();

        //	Send the SMB request
        sess.getSession().Send(m_smbbuf, getLength());
    }

    /**
     * Set the secondary SMB command
     *
     * @param cmd Secondary SMB command code.
     */
    public final void setAndXCommand(int cmd) {

        //	Set the chained command packet type
        m_smbbuf[ANDXCOMMAND] = (byte) cmd;
        m_smbbuf[ANDXRESERVED] = (byte) 0;

        //	If the AndX command is disabled clear the offset to the chained packet
        if (cmd == PacketTypeV1.NoChainedCommand)
            setParameter(1, 0);
    }

    /**
     * Set the data byte count for this SMB packet
     *
     * @param cnt Data byte count.
     */
    public final void setByteCount(int cnt) {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort(cnt, m_smbbuf, offset);
    }

    /**
     * Set the data byte count for this SMB packet
     */

    public final void setByteCount() {
        int offset = getByteOffset() - 2;
        int len = m_pos - getByteOffset();
        DataPacker.putIntelShort(len, m_smbbuf, offset);
    }

    /**
     * Set the data byte area in the SMB packet
     *
     * @param byts Byte array containing the data to be copied to the SMB packet.
     */
    public final void setBytes(byte[] byts) {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort(byts.length, m_smbbuf, offset);

        offset += 2;

        for (int idx = 0; idx < byts.length; m_smbbuf[offset + idx] = byts[idx++]) ;
    }

    /**
     * Set the SMB command
     *
     * @param cmd SMB command code
     */
    public final void setCommand(int cmd) {
        m_pkttype = cmd;
        m_smbbuf[COMMAND] = (byte) cmd;
    }

    /**
     * Set the SMB error class.
     *
     * @param cl SMB error class.
     */
    public final void setErrorClass(int cl) {
        m_smbbuf[ERRORCLASS] = (byte) (cl & 0xFF);
    }

    /**
     * Set the SMB error code
     *
     * @param sts SMB error code.
     */
    public final void setErrorCode(int sts) {
        m_smbbuf[ERROR] = (byte) (sts & 0xFF);
    }

    /**
     * Set the SMB flags value.
     *
     * @param flg SMB flags value.
     */
    public final void setFlags(int flg) {
        m_smbbuf[FLAGS] = (byte) flg;
    }

    /**
     * Set the SMB flags2 value.
     *
     * @param flg SMB flags2 value.
     */
    public final void setFlags2(int flg) {
        DataPacker.putIntelShort(flg, m_smbbuf, FLAGS2);
    }

    /**
     * Set the multiplex identifier.
     *
     * @param mid Multiplex identifier
     */
    public final void setMultiplexId(int mid) {
        DataPacker.putIntelShort(mid, m_smbbuf, MID);
    }

    /**
     * Set the specified parameter word.
     *
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */
    public final void setParameter(int idx, int val) {
        int pos = WORDCNT + (2 * idx) + 1;
        DataPacker.putIntelShort(val, m_smbbuf, pos);
    }

    /**
     * Set the specified parameter words.
     *
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */

    public final void setParameterLong(int idx, int val) {
        int pos = WORDCNT + (2 * idx) + 1;
        DataPacker.putIntelInt(val, m_smbbuf, pos);
    }

    /**
     * Set the parameter count
     *
     * @param cnt Parameter word count.
     */
    public final void setParameterCount(int cnt) {
        m_smbbuf[WORDCNT] = (byte) cnt;
    }

    /**
     * Set the process identifier value (PID).
     *
     * @param pid Process identifier value.
     */
    public final void setProcessId(int pid) {
        DataPacker.putIntelShort(pid, m_smbbuf, PID);
    }

    /**
     * Set the packet sequence number, for connectionless commands.
     *
     * @param seq Sequence number.
     */
    public final void setSeqNo(int seq) {
        DataPacker.putIntelShort(seq, m_smbbuf, SEQNO);
    }

    /**
     * Set the session id.
     *
     * @param sid Session id.
     */
    public final void setSID(int sid) {
        DataPacker.putIntelShort(sid, m_smbbuf, SID);
    }

    /**
     * Set the tree identifier (TID)
     *
     * @param tid Tree identifier value.
     */
    public final void setTreeId(int tid) {
        DataPacker.putIntelShort(tid, m_smbbuf, TID);
    }

    /**
     * Set the user identifier (UID)
     *
     * @param uid User identifier value.
     */
    public final void setUserId(int uid) {
        DataPacker.putIntelShort(uid, m_smbbuf, UID);
    }

    /**
     * Align the byte area pointer on an int (32bit) boundary
     */
    public final void alignBytePointer() {
        m_pos = DataPacker.longwordAlign(m_pos);
    }

    /**
     * Reset the byte/parameter pointer area for packing/unpacking data items from the packet
     */
    public final void resetBytePointer() {
        m_pos = getByteOffset();
        m_endpos = m_pos + getByteCount();
    }

    /**
     * Reset the byte/parameter pointer area for packing/unpacking data items from the packet, and align
     * the buffer on an int (32bit) boundary
     */
    public final void resetBytePointerAlign() {
        m_pos = DataPacker.longwordAlign(getByteOffset());
        m_endpos = m_pos + getByteCount();
    }

    /**
     * Reset the byte/parameter pointer area for packing/unpacking paramaters from the packet
     */
    public final void resetParameterPointer() {
        m_pos = PARAMWORDS;
    }

    /**
     * Set the unpack pointer to the specified offset, for AndX processing
     *
     * @param off int
     * @param len int
     */
    public final void setBytePointer(int off, int len) {
        m_pos = off;
        m_endpos = m_pos + len;
    }

    /**
     * Skip a number of bytes in the parameter/byte area
     *
     * @param cnt int
     */
    public final void skipBytes(int cnt) {
        m_pos += cnt;
    }

    /**
     * Return the flags value as a string
     *
     * @return String
     */
    protected final String getFlagsAsString() {

        //	Get the flags value
        int flags = getFlags();
        if (flags == 0)
            return "<None>";

        StringBuffer str = new StringBuffer();
        if ((flags & FLG_SUBDIALECT) != 0)
            str.append("SubDialect,");

        if ((flags & FLG_CASELESS) != 0)
            str.append("Caseless,");

        if ((flags & FLG_CANONICAL) != 0)
            str.append("Canonical,");

        if ((flags & FLG_OPLOCK) != 0)
            str.append("Oplock,");

        if ((flags & FLG_NOTIFY) != 0)
            str.append("Notify,");

        if ((flags & FLG_RESPONSE) != 0)
            str.append("Response,");

        str.setLength(str.length() - 1);

        return str.toString();
    }

    /**
     * Return the flags2 value as a string
     *
     * @return String
     */
    protected final String getFlags2AsString() {

        //	Get the flags2 value
        int flags2 = getFlags2();

        if (flags2 == 0)
            return "<None>";

        StringBuffer str = new StringBuffer();

        if ((flags2 & FLG2_LONGFILENAMES) != 0)
            str.append("LongFilenames,");

        if ((flags2 & FLG2_EXTENDEDATTRIB) != 0)
            str.append("ExtAttributes,");

        if ((flags2 & FLG2_READIFEXE) != 0)
            str.append("ReadIfEXE,");

        if ((flags2 & FLG2_LONGERRORCODE) != 0)
            str.append("LongErrorCode,");

        if ((flags2 & FLG2_UNICODE) != 0)
            str.append("Unicode,");

        str.setLength(str.length() - 1);

        return str.toString();
    }
}
